SOC是一个端到端的服务,为RTOS、Linux、Android操作系统的物联网终端提供安全审计。本文介绍了如何在RTOS操作系统中集成基础版SOC SDK。
背景信息
基础版SOC SDK是SOC在终端的代理,可以定期检查设备系统的完整性,实时上报运行状态。让管理者随时了解物联网终端的安全状况,及时发现安全问题,进行排查和修复。
基础版SOC SDK通过Core、Platform、Service三个层次,提供了应用集成,平台移植和服务扩展功能,旨在通过SOC服务一站式地保护物联网终端安全。具体架构如下图所示:
前提条件
已通过IoT安全中心获取安全SDK,并根据目标系统的开发要求将SDK解压到特定位置。
集成基础版SOC后,调用接口int32_t aiot_das_stepping
会上报MQTT消息。您可以通过控制调用接口的频率来控制设备端基础版SOC发送的消息量。
平台适配
步骤一:设置日志开关
编译时传入DAS_DEBUG,即可开启基础版SOC SDK日志。基础版SOC SDK代码中打印日志接口为DAS_LOG(...)定义在das/include/das/platform.h
。
步骤二:设置设备身份信息
请适配物联网终端唯一ID,协助基础版SOC SDK识别物联网终端,ID可以是MEID、CPU ID、MAC地址等等,形式为字符串即可。
以移远ec600s开发板为例,函数原型如下:
适配接口在das/src/board/ec600s/das_board.c
size_t das_hal_device_id(char *buf, size_t size);
入参信息如下:
参数 | 描述 |
buf | 用来存放物联网终端唯一ID的缓存。 |
size | buf的大小,目前为96字节,您可以自行修改DEVICE_ID_MAX_LEN,但由于物联网终端唯一ID会上报到云端,请配合您云端通道(如MQTT)的单笔消息频宽设定。 |
返回结果如下:
请求结果 | 描述 |
成功 | 返回实际写入buf的ID大小,以字节为单位。 |
失败 | 请返回0。 |
步骤三:适配ROM扫描范围
如果您有text或者ROM code,您希望确保完整性,可以适配此接口,基础版SOC SDK将定期帮您扫描计算完整性。
以移远ec600s开发板为例,函数原型如下:
适配接口在das/src/board/ec600s/das_board.c
。
int das_hal_rom_info(das_rom_bank_t banks[DAS_ROM_BANK_NUMBER]);
入参信息如下:
参数 | 描述 |
buf | 用来存放物联网终端唯一ID的缓存。 |
返回结果如下:
请求结果 | 描述 |
成功 | 请返回DAS_ROM_BANK_NUMBER。 |
失败 | 请返回0。 |
可以参考如下案例:
您有两段代码段想要扫描,且您认为这两段代码段正常情况下不该被修改(除非返厂重烧),如text、code等等,那么您可以可在das_hal_rom_info设置它。代码段的位置可根据您的layout file(如scatter file)所编写的symbol位置去设定。
针对设定的代码段,您的任务必须有可读权限,以免发生CPU异常。
extern uint32_t __flash_text_start__;
extern uint32_t __flash_text_end__;
extern uint32_t __ram_image2_text_start__;
extern uint32_t __ram_image2_text_end__;
int das_hal_rom_info(das_rom_bank_t banks[DAS_ROM_BANK_NUMBER])
{
banks[0].address = &__flash_text_start__;
banks[0].size = &__flash_text_end__ - &__flash_text_start__;
banks[1].address = &__ram_image2_text_start__;
banks[1].size = &__ram_image2_text_end__ - &__ram_image2_text_start__;
return 2;
}
步骤四:互斥锁适配
请在das/src/service/service_sys.c
和das/src/service/service_lwip_nfi.c
文档中适配互斥锁,例如POSIX线程pthread或者AliOS-Things互斥锁aos_mutex。
步骤五:网络流量采集适配
根据不同的网络环境,选择对应的开发方式:
如果您的网路栈是基于LWIP,且可以修改到LWIP协议栈,请参考基于LWIP的网路流采集篇章做网路监控挂载。
如果您使用其他网络,可以采用自定义网路流采集,根据您的网路栈做挂载,或者挂载在AT命令负责派送网路的出入口。
您挂载的任务可能和您的MQTT任务不同,请将das/src/core/das_attest.c
和将被挂载的任务一起编译。das_attest.c会将数据写到一个全局缓存,基础版SOC SDK将从此全局缓存读取数据。
基于LWIP的网路流采集
如果您的网路栈是基于LWIP,且您可以修改到LWIP协议栈,您可采用此网路流采集方式。常见RTOS如FreeRTOS和AliOS-Things上,都是采用LWIP协议栈。基础版SOC SDK提供das_attest API对网络行为进行监控。主要原理是在系统的LWIP协议栈中,插入das_attest审计代码,从而记录网络流的进程。
TCP网络流量进入
在tcp_in.c文件开头引入相关头文件。
#include "lwip/ip.h" #include <das.h>
在tcp_in.c文件的tcp_input函数中插入das_attest监控代码。
void tcp_input(struct pbuf *p, struct netif *inp) { .... /* Convert fields in TCP header to host byte order. */ tcphdr->src = ntohs(tcphdr->src); tcphdr->dest = ntohs(tcphdr->dest); seqno = tcphdr->seqno = ntohl(tcphdr->seqno); ackno = tcphdr->ackno = ntohl(tcphdr->ackno); tcphdr->wnd = ntohs(tcphdr->wnd); // 插入如下代码 { das_attest("NFI_INPUT", ip_current_src_addr()->addr, ip_current_dest_addr()->addr, tcphdr->src, tcphdr->dest, p->tot_len, ip_current_header_proto() ); // } flags = TCPH_FLAGS(tcphdr); tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0); /* Demultiplex an incoming segment. First, we check if it is destined for an active connection. */ prev = NULL; ... }
UDP协议流量进入
在udp.c文件开头引入相关头文件。
#include "lwip/ip.h" #include <das.h>
在udp.c文件的udp_input函数中插入das_attest监控代码。
void udp_input(struct pbuf *p, struct netif *inp) { ... LWIP_DEBUGF(UDP_DEBUG, ("udp_input: received datagram of length %"U16_F"\n", p->tot_len)); /* convert src and dest ports to host byte order */ src = ntohs(udphdr->src); dest = ntohs(udphdr->dest); udp_debug_print(udphdr); // 插入如下代码 { das_attest("NFI_INPUT", ip_current_src_addr()->addr, ip_current_dest_addr()->addr, src, dest, p->tot_len, ip_current_header_proto() ); // } /* print the UDP source and destination */ LWIP_DEBUGF(UDP_DEBUG, ("udp (%"U16_F".%"U16_F".%"U16_F".%"U16_F", %"U16_F") <-- " "(%"U16_F".%"U16_F".%"U16_F".%"U16_F", %"U16_F")\n", ip4_addr1_16(&iphdr->dest), ip4_addr2_16(&iphdr->dest), ip4_addr3_16(&iphdr->dest), ip4_addr4_16(&iphdr->dest), ntohs(udphdr->dest), ip4_addr1_16(&iphdr->src), ip4_addr2_16(&iphdr->src), ip4_addr3_16(&iphdr->src), ip4_addr4_16(&iphdr->src), ntohs(udphdr->src))); ... }
网络流量出方向
在ip.c文件开头引入相关头文件。
#include <das.h>
在ip.c文件的ip_output_if_opt函数中插入das_attest监控代码。
err_t ip_output_if_opt(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest, u8_t ttl, u8_t tos, u8_t proto, struct netif *netif, void *ip_options, u16_t optlen) { ... LWIP_DEBUGF(IP_DEBUG, ("netif->output()")); // 插入如下代码 { { u16_t sport = 0, dport = 0; u16_t len = 0; u16_t iphdr_hlen = IPH_HL(iphdr); iphdr_hlen *= 4; if (proto == IP_PROTO_UDP || IPH_PROTO(iphdr) == IP_PROTO_UDP) { struct udp_hdr *udphdr = (struct udp_hdr *)((u8_t *)iphdr + iphdr_hlen); if (udphdr) { sport = lwip_ntohs(udphdr->src); dport = lwip_ntohs(udphdr->dest); len = (p->tot_len > iphdr_hlen) ? (p->tot_len - iphdr_hlen) : p->tot_len; } } else if (proto == IP_PROTO_TCP || IPH_PROTO(iphdr) == IP_PROTO_TCP) { struct tcp_hdr *tcphdr = (struct tcp_hdr *)((u8_t *)iphdr + iphdr_hlen); if (tcphdr) { sport = lwip_ntohs(tcphdr->src); dport = lwip_ntohs(tcphdr->dest); len = (p->tot_len > iphdr_hlen) ? (p->tot_len - iphdr_hlen) : p->tot_len; } } if (dport) { das_attest("NFI_OUTPUT", src->addr, dest->addr, sport, dport, len, proto ); } } // } return netif->output(netif, p, dest); }
自定义网路流采集
如果您的网路栈不是基于LWIP,或者您无法修改LWIP协议栈,您可采用自定义网路流采集方式。您需要提供您每笔网路流的源IP、源端口、目标IP、目标端口、协议。您可根据您的网路栈做挂载,或者挂载在AT命令负责派送网路的出入口等等也行。
挂载TCP网络流量进方向
#include "das.h" #include "inet.h" // ... struct in_addr remote_addr; struct in_addr local_addr; inet_aton(remote_ip, &remote_addr); inet_aton(local_ip, &local_addr); das_attest("NFI_INPUT", remote_addr.s_addr, // 外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。 local_addr.s_addr, // 本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。 remote_port, // 外部端口,uint32_t。 local_port, // 本机端口,uint32_t。 packet_len, // 包长度,uint32_t。 6 // 填写 6,代表 TCP,uint32_t。 ); // ...
挂载TCP网络流量出方向
#include "das.h" #include "inet.h" // ... struct in_addr remote_addr; struct in_addr local_addr; inet_aton(remote_ip, &remote_addr); inet_aton(local_ip, &local_addr); das_attest("NFI_OUTPUT", local_addr.s_addr, // 反过来,本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。 remote_addr.s_addr, // 反过来,外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。 local_port, // 反过来,本机端口,uint32_t。 remote_port, // 反过来,外部端口,uint32_t。 packet_len, // 包长度,uint32_t。 6 // 填写 6,代表 TCP,uint32_t。 ); // ...
挂载UDP网络流量进方向
#include "das.h" #include "inet.h" // ... struct in_addr remote_addr; struct in_addr local_addr; inet_aton(remote_ip, &remote_addr); inet_aton(local_ip, &local_addr); das_attest("NFI_INPUT", remote_addr.s_addr, // 外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。 local_addr.s_addr, // 本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。 remote_port, // 外部端口,uint32_t。 local_port, // 本机端口,uint32_t。 packet_len, // 包长度,uint32_t。 17 // 填写 17,代表 UDP,uint32_t。 ); // ...
挂载UDP网络流量出方向
#include "das.h" #include "inet.h" // ... struct in_addr remote_addr; struct in_addr local_addr; inet_aton(remote_ip, &remote_addr); inet_aton(local_ip, &local_addr); das_attest("NFI_OUTPUT", local_addr.s_addr, // 反过来,本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。 remote_addr.s_addr, // 反过来,外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。 local_port, // 反过来,本机端口,uint32_t。 remote_port, // 反过来,外部端口,uint32_t。 packet_len, // 包长度,uint32_t。 17 // 填写 17,代表 UDP,uint32_t。 ); // ...
应用集成
在物联网终端的实际开发中,由于资源受限,每个物联网终端网络连接的数目是受限的。基础版SOC SDK在核心层只定义了相关数据的订阅和分发接口,物联网终端可以根据实际的网络会话上对接基础版SOC服务。
步骤一:初始化核心服务
请参考如下函数原型:
void* das_init(const char *product_name, const char *device_name);
入参信息如下:
参数 | 描述 |
product_name | 产品名称。如果使用阿里云IoT平台上云,参数可以在阿里云IoT平台上申请获得;如果自己实现上云通道,参数自定义即可。 |
device_name | 物联网终端名称。如果使用阿里云IoT平台上云,参数可以在阿里云IoT平台上申请获得;如果自己实现上云通道,参数自定义即可。 |
返回结果如下:
请求结果 | 描述 |
成功 | 初始化成功,返回服务的session指针,用于后续安全服务相关函数调用。 |
失败 | 返回NULL。 |
步骤二:设置固件版本号
请参考如下函数原型:
int das_set_firmware_version(char *ver);
入参信息如下:
参数 | 描述 |
ver | 固件版本号字串,例如:lemo-1.0.0-20191009.1111。 |
返回结果如下:
请求结果 | 描述 |
成功 | 返回0。 |
失败 | 返回-1。 |
步骤三:设置上下行消息主题
服务器端和客户端根据消息的topic类型来确认是否是自己需要处理的数据。
请参考如下函数原型:
设置上行消息主题
const char* das_pub_topic(void *session, const char *topic);
设置下行消息主题
const char* das_sub_topic(void *session, const char *topic);
入参信息如下:
参数
描述
session
由das_init函数返回的服务实例。
topic
自定义的上下行消息topic字符串;如果想使用内置的缺省topic,则此值为NULL。
返回结果如下:
请求结果
描述
成功
返回默认或自定义的topic字符串。
说明如果topic参数为NULL,返回内置的缺省topic字符串,原型如下:
/sys/$(product_name)/$(device_name)/security/up(down)stream
。自定义topic字符串长度不能超过64字节,否则设置会失败。
失败
返回NULL。有可能是参数错误或者topic字符串超过64字节。
步骤四:配置网络连接
基础版SOC SDK采集的数据需要通过业务已有的网络通道进行上报。
请参考如下函数原型:
void das_connection(void *session,
publish_handle_t publish_handle,
void *channel);
入参信息如下:
参数 | 描述 |
session | 由das_init函数返回的实例。 |
publish_handle | 需要用户自己实现,用来发送数据的函数指针。 |
channel | 业务创建,用于数据上报的网络通道实例。 |
本函数无返回值。
步骤五:消息发送回调
用来发送数据的回调函数。如果基础版SOC SDK有数据需要上报,那么该回调由安全服务内部触发。请将此回调通过das_connection注册。
请参考如下函数原型:
typedef int (*publish_handle_t)(const char *topic,
const uint8_t *message, size_t msg_size,
void *channel);
该接口需要用户实现。
入参信息如下:
参数 | 描述 |
topic | 数据上行的topic类型。 |
message | 需要发送的数据。 |
size | 需要发送的数据大小。 |
channnel | 业务创建,用于数据上报的网络通道实例。 |
返回结果如下:
请求结果 | 描述 |
成功 | 返回0。 |
失败 | 返回负数。 |
步骤六:更新网络连接状态
当业务的网络状态发生变化时,需要通知基础版SOC SDK。
请参考如下函数原型:
业务网络已连接
void das_on_connected(void *session);
业务网络已断开
void das_on_connected(void *session);
入参信息如下:
参数
描述
session
由das_init函数返回的实例。
本函数无返回值。
步骤七:处理下行数据
当业务收到服务器下发的指令或数据的时候,可以通过topic来区分是否是SOC的。如果是,则需要通知基础版SOC SDK来处理。
请参考如下函数原型:
void das_on_message(void *session, const uint8_t *message, size_t msg_size);
入参信息如下:
参数 | 描述 |
session | 由das_init函数返回的实例。 |
message | 由服务器端下发的数据。 |
size | 下发数据的字节数。 |
函数无返回值。
步骤八:步进驱动取证服务
基础版SOC SDK不会主动执行取证操作,而是需要业务定时来驱动执行。
请参考如下函数原型:
das_result_t das_stepping(void *session, uint64_t now);
入参信息如下:
参数 | 描述 |
session | 由das_init函数返回的实例。 |
now | 当前系统时间,以毫秒为单位。 |
返回结果如下:
请求结果 | 描述 |
成功 | 返回0,int(das_result_t)类型。 |
失败 | 返回负数,int(das_result_t)类型。 |
步骤九:终止核心服务
请参考如下函数原型:
void das_final(void *session);
入参信息如下:
参数 | 描述 |
session | 由das_init函数返回的实例。 |
函数无返回值。
示例代码
完整示例代码请参看$(das_sdk)/example/lv-example/mqtt_das_example.c
。
#include "iot_import.h"
#define PRODUCT_KEY "demo_das_product"
#define DEVICE_NAME "demo_das_device_1"
static void *session = null;
static int _on_publish(const char *topic, uint8_t *msg, uint32_t size, void *mqtt)
{
iotx_mqtt_topic_info_t topic_msg;
topic_msg.qos = IOTX_MQTT_QOS1;
topic_msg.payload = (void *)message;
topic_msg.payload_len = length;
return IOT_MQTT_Publish(mqtt, topic, &topic_msg);
}
static void on_message(void *handle, void *pclient, iotx_mqtt_event_msg_pt msg)
{
das_on_message(session, msg.payload, msg.payload_len);
}
int main(int argc, const char argv[][])
{
const char *sub_topic;
mqtt = IOT_MQTT_Construct(&mqtt_params);
session = das_init(PRODUCT_KEY, DEVICE_NAME);
das_set_firmware_version("lemo-1.0.0-20191009.1111");
sub_topic = das_sub_topic(session, NULL);
das_connection(session, _on_publish, mqtt);
das_on_connected(session);
IOT_MQTT_Subscribe(mqtt,
sub_topic, IOTX_MQTT_QOS1, on_message, session);
while (IOT_MQTT_Yield(mqtt, 200) != IOT_MQTT_DISCONNECTED) {
...
das_stepping(session, time(NULL));
...
}
das_on_disconnected(session);
das_final(session);
return 0;
}